'''
Generates a JobDef from an XML Settings file.
'''
from xml.dom.minidom import parse
#======================================================================
# COMMON TEMPLATE
#======================================================================
COMMON_TEMPLATE = """ """
#======================================================================
# DEV TASK TEMPLATE
#======================================================================
# No replacements
DEV_TASK_TEMPLATE = """ 
'''
Development Task 
The development task generates a phenotype using the parameters in the genotype.
'''
import time
import random
import os
from dexen import BaseSlaveTask
from individual import Individual
from slave_tasks import common
common.enable_hou_module()
import hou

#Function to run Houdini
def run_houdini_dev(ind):
    #open the hipnc file
    hou_file_path = os.path.join(common.HOU_FOLDER_PATH, common.HOU_DEV_FILE_NAME)
    hou.hipFile.load(hou_file_path)
    #set the parameters using the individual's genes
    genotype_node = hou.node(common.GENOTYPE_NODE_PATH)
    for i in range(len(ind.genotype)):
        genotype_node.setParms({"gene_"+str(i+1):ind.genotype[i]})
    #save phenotype to file
    phen_folder_path = os.path.join(common.HOU_FOLDER_PATH, "temp")
    common.create_folder(phen_folder_path)
    phen_file_path = os.path.join(phen_folder_path, "temp.bgeo")
    phenotype_node = hou.node(common.PHENOTYPE_NODE_PATH)
    phenotype_node.setParms(dict([["file",phen_file_path]]))
    phenotype_node.cook()
    #read the phenotype file
    f = open(phen_file_path, "rb")
    phenotype = f.read()
    f.close()
    #return the phenotype (as a string)
    return phenotype
    
#Slave task for development
class DevTask(BaseSlaveTask):
    def __init__(self):
        pass
    def __execute__(self, master_state, inds):
        #process individuals
        for ind in inds:
            assert isinstance(ind, Individual)
            ind.phenotype = run_houdini_dev(ind)
        #return the inds with the phenotype added
        return inds
"""
#======================================================================
# EVAL TASK TEMPLATE
#======================================================================
# No replacements
EVAL_TASK_TEMPLATE = """
'''
Evaluation Tasks
The evaluation tasks generate scores for phenotypes.
'''
import time
import random
import os
from dexen import BaseSlaveTask
from individual import Individual
from slave_tasks import common
common.enable_hou_module()
import hou

#Function to run Houdini
def run_houdini_eval(ind, file_name):
    #write the phenotype to a temporary file
    phen_folder_path = os.path.join(common.HOU_FOLDER_PATH, "temp")
    common.create_folder(phen_folder_path)
    phen_file_path = os.path.join(phen_folder_path, "temp.bgeo")
    f = open(phen_file_path, "wb")
    f.write(ind.phenotype)
    f.close()
    #open the hipnc file
    hou_file_path = os.path.join(common.HOU_FOLDER_PATH, file_name)
    hou.hipFile.load(hou_file_path)
    #load the geometry into the phenotype node
    phenotype_node = hou.node(common.PHENOTYPE_NODE_PATH)
    phenotype_node.setParms(dict([["file",phen_file_path]]))
    #get the result from the score node
    score_node = hou.node(common.SCORE_NODE_PATH)
    score_node.cook()
    score = score_node.geometry().attribValue(common.SCORE_ATTRIB_NAME)
    #return the score
    return score
"""
# $FILE_NAME needs to be replaced
EVAL_TASK_CLASS_TEMPLATE = """
#Slave task for evaluation A
class EvalATask(BaseSlaveTask):
    def __init__(self):
        pass
    def __execute__(self, master_state, inds):
        #process individuals
        for ind in inds:
            assert isinstance(ind, Individual)
            ind.evalA = run_houdini_eval(ind, common.$FILE_NAME)
        #return the inds with the scores added 
        return inds
"""
#======================================================================
# FEEDBACK TASK TEMPLATE
#======================================================================
# $REGISTER_SCORE needs to be replaced
FEEDBACK_TASK_TEMPLATE = """
'''
Evaluation Tasks
The feedback task performs selection and reproduction.
'''
import random
from dexen import BaseSlaveTask
from dexen.libs import ranking
from dexen.libs.ranking import ParetoRanking
import settings

#Function to create new individuals
def create_children(ranked):
    parents = [x.ind for x in ranked[:len(ranked)/2]]
    assert len(parents) % 2 == 0
    random.shuffle(parents)
    children = []
    for i in range(0, len(parents), 2):
        child1, child2 = parents[i].reproduce(parents[i+1])
        children.append(child1)
        children.append(child2)
    return children

#Function to create new individuals
def kill_individuals(ranked):
    deceased = [x.ind for x in ranked[len(ranked)/2:]]
    for ind in deceased:
        ind.kill()

#Slave task for feedback
class FeedbackTask(BaseSlaveTask):
    def __init__(self):
        pass
    def __execute__(self, master_state, inds):
        #rank individuals
        p_ranking = ParetoRanking(inds)
        $REGISTER_SCORE
        ranked = p_ranking.rank() # returns list of RankResult sorted by rank
        #create children using high rank individuals
        children = create_children(ranked)
        #kill the low rank individuals
        kill_individuals(ranked)
        #return all the inds (inds + children)
        return inds + children
"""
#======================================================================
# MASTER TASK TEMPLATE
#======================================================================
# $REGISTER_TASKS needs to be replaced
MASTER_TASK_TEMPLATE = """
'''
Master task
Registers slave tasks.
'''
import traceback
import dexen
import conditions
import  
from individual import Individual
from slave_tasks.dev import DevTask
from slave_tasks.eval import EvalATask, EvalBTask
from slave_tasks.feedback import FeedbackTask

# Master task
class MasterTask(dexen.BaseMasterTask):
    def __init__(self, master_env):
        #register slave tasks
        try:
            assert isinstance(master_env, dexen.MasterEnv)
            $REGISTER_TASKS
        except:
            traceback.print_tb(sys.exc_info()[2])
        #initialise population
        for _ in range(settings.POP_SIZE):
            ind = Individual()
            master_env.set_ind(ind)
        
    def __execute__(self, master_env):
        assert isinstance(master_env, dexen.MasterEnv)
        pop_size = master_env.num_inds()
        if pop_size >= settings.MAX_BIRTHS:
            master_env.stop_master()

"""
#======================================================================
# CONDITIONS TEMPLATE
#======================================================================
CONDITIONS_TEMPLATE = """ """
#======================================================================
# INDIVIDUAL TEMPLATE
#======================================================================
INDIVIDUAL_TEMPLATE = """ """
#======================================================================
# EXAMPLE XML FILE
#======================================================================
"""
<!-- Settings file for Dexen Houdini generator -->
<dexen_schema>
    <generator type="houdini" algorithm="ea"/>
    <population initial_size="100" max_births="10000" />
    <devTask label="Development" file="dev.hipnc" input_size="10"/>
    <evalTask label="Eval Energy" file="energy.hipnc" input_size="10"/>
    <evalTask label="Eval Daylight" file="daylight.hipnc" input_size="10"/>
    <feedbackTask label="Feedback" input_size="50" ranking="pareto_ranking"/>
    <genotype>
        <geneRange from_pos="1" to_pos="5" type="float_range" min="0" max="1" />
        <geneRange from_pos="6" to_pos="22" type="int_range" min="5" max="18" />
        <geneRange from_pos="23" to_pos="28" type="int_choice">1,3,5,7,12,44,56</geneRange>
        <geneRange from_pos="29" to_pos="30" type="string_choice" >concrete,glass,brick,timber</geneRange>
    </genotype>
</dexen_schema>
"""
#======================================================================
# XML SETTINGS CLASSES
#======================================================================
class XmlSettings(object):
    def __init__(self):
        self.generator_settings = None
        self.general_settings = None
        self.dev_task_settings = None
        self.feedback_task_settings = None
        self.eval_tasks_settings = []
        self.genotype_settings = []
class GeneratorSettings(object):
    def __init__(self,generator_type,algorithm):
        self.generator_type = str(generator_type)
        self.algorithm = str(algorithm)
class PopulationSettings(object):
    def __init__(self,initial_size,max_births):
        self.initial_size = int(initial_size)
        self.max_births = int(max_births)
class DevTaskSettings(object):
    def __init__(self,label,input_size,file_name):
        self.label = str(label)
        self.file_name = str(file_name)
        self.input_size = int(input_size)
class EvalTaskSettings(object):
    def __init__(self,label,input_size,file_name):
        self.label = str(label)
        self.file_name = str(file_name)
        self.input_size = int(input_size)
class FeedbackTaskSettings(object):
    def __init__(self,label,input_size,ranking):
        self.label = str(label)
        self.input_size = int(input_size)
        self.ranking = str(ranking)
class GeneSetFloatRange(object):
    def __init__(self,from_pos,to_pos,min,max):
        self.from_pos = int(from_pos)
        self.to_pos = int(to_pos)
        self.min = float(min)
        self.max = float(max)
class GeneSetIntRange(object):
    def __init__(self,from_pos,to_pos,min,max):
        self.from_pos = int(from_pos)
        self.to_pos = int(to_pos)
        self.min = int(min)
        self.max = int(max)
class GeneSetIntChoice(object):
    def __init__(self,from_pos,to_pos,choices):
        self.from_pos = int(from_pos)
        self.to_pos = int(to_pos)
        self.choices = map(int, choices)
class GeneSetStringChoice(object):
    def __init__(self,from_pos,to_pos,choices):
        self.from_pos = int(from_pos)
        self.to_pos = int(to_pos)
        self.choices = map(str, choices)
#======================================================================
# XML GENERATOR
#======================================================================
class HoudiniGenerator(object):
    def __init__(self, schema_dir):
        self.schema_dir = schema_dir
        
    def generate(self, schema_name="schema.zip"):
        pass
    
    def read_xml_settings(xml_file_path):
        dom = parse(xml_file_path)
        xml_settings = XmlSettings()
        #===========================================================================
        #<generator type="houdini" algorithm="ea"/>
        elem = dom.getElementsByTagName("generator")[0]
        generator_type = elem.getAttribute( "type")
        generator_algorithm = elem.getAttribute( "algorithm")
        xml_settings.generator_settings = GeneratorSettings(
            generator_type,generator_algorithm)
        #===========================================================================
        #<population initial_size="100" max_births="10000" genotype_length="55"/>
        elem = dom.getElementsByTagName("population")[0]
        population_initial_size = elem.getAttribute( "initial_size")
        population_max_births = elem.getAttribute( "max_births")
        xml_settings.general_settings = PopulationSettings(
            population_initial_size,population_max_births)
        #===========================================================================
        #<devTask label="Development" input_size="10" file="dev.hipnc"/>
        elem = dom.getElementsByTagName("devTask")[0]
        dev_task_label = elem.getAttribute( "label")
        dev_task_input_size = elem.getAttribute( "input_size")
        dev_task_file = elem.getAttribute( "file")
        xml_settings.dev_task_settings = DevTaskSettings(
            dev_task_label,dev_task_input_size,dev_task_file)
        #===========================================================================
        #<feedbackTask label="Feedback" input_size="50" ranking="pareto_ranking"/>
        elem = dom.getElementsByTagName("feedbackTask")[0]
        feedback_task_label = elem.getAttribute( "label")
        feedback_task_input_size = elem.getAttribute( "input_size")
        feedback_task_ranking = elem.getAttribute( "ranking")
        xml_settings.feedback_task_settings = FeedbackTaskSettings(
            feedback_task_label,feedback_task_input_size,feedback_task_ranking)
        #===========================================================================
        #<evalTask label="Eval Energy" file="energy.hipnc" input_size="10"/>
        elems = dom.getElementsByTagName("evalTask")
        eval_tasks = []
        for elem in elems:
            eval_task_label = elem.getAttribute( "label")
            eval_task_input_size = elem.getAttribute( "input_size")
            eval_task_file = elem.getAttribute( "file")
            xml_settings.eval_tasks_settings.append(
                EvalTaskSettings(eval_task_label,eval_task_input_size,eval_task_file))
        #===========================================================================
        #<geneSet from_pos="1" to_pos="5" type="float_range" min="0" max="1" />
        #<geneSet from_pos="6" to_pos="22" type="int_range" min="5" max="18" />
        #<geneSet from_pos="23" to_pos="28" type="int_choice">1,3,5,7,12,44,56</geneType>
        #<geneSet from_pos="29" to_pos="30" type="string_choice" >concrete,glass,brick,timber</geneType>
        elem = dom.getElementsByTagName("genotype")[0]
        elems = elem.getElementsByTagName("geneSet")
        gene_ranges = []
        for elem in elems:
            gene_set_from_pos = elem.getAttribute( "from_pos")
            gene_set_to_pos = elem.getAttribute( "to_pos")
            gene_set_type = elem.getAttribute( "type")
            gene_set_settings = None
            if gene_set_type == "float_range":
                gene_set_min = elem.getAttribute( "min")
                gene_set_max = elem.getAttribute( "max")
                gene_set_settings = GeneSetFloatRange(
                    gene_set_from_pos, gene_set_to_pos, gene_set_min, gene_set_max)
            elif gene_set_type == "int_range":
                gene_set_min = elem.getAttribute( "min")
                gene_set_max = elem.getAttribute( "max")
                gene_set_settings = GeneSetIntRange(
                    gene_set_from_pos, gene_set_to_pos, gene_set_min, gene_set_max)
            elif gene_set_type == "int_choice":
                gene_set_data = elem.childNodes[0].data.split(',')
                gene_set_settings = GeneSetIntChoice(
                    gene_set_from_pos, gene_set_to_pos, gene_set_data)
            elif gene_set_type == "string_choice":
                gene_set_data = elem.childNodes[0].data.split(',')
                gene_set_settings = GeneSetStringChoice(
                    gene_set_from_pos, gene_set_to_pos, gene_set_data)
            xml_settings.feedback_task_settings.append(gene_set_settings)
        #===========================================================================
        return xml_settings